package com.cyou.dream.core;

import com.cyou.dream.configuration.RiskConfig;
import com.cyou.dream.configuration.RiskYamlConfig;
import com.cyou.dream.configuration.StrategyConfig;
import com.cyou.dream.define.EStrategy;
import com.cyou.dream.define.RedisKeys;
import com.cyou.dream.logging.LogMarkers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
@Log4j2
public class StrategyLoader {

    private volatile Map<String, StrategyConfig> strategyMap = new HashMap<>();

    private volatile Integer currentConfigVersion = -1;

    private final RedisTemplate<String, String> redisTemplate;

    private final ObjectMapper yamlMapper;

    private final RiskConfig initialConfig;

    public StrategyLoader(RiskConfig riskConfig, RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.yamlMapper = new ObjectMapper(new YAMLFactory());
        this.initialConfig = riskConfig;

        loadStrategies();
    }

    protected StrategyConfig getStrategy(String strategyId) {
        return Optional.ofNullable(strategyMap.get(strategyId))
                .orElseThrow(() -> new IllegalArgumentException("Strategy not found: " + strategyId));
    }

    protected RiskConfig reloadStrategies() {
        return loadStrategies();
    }

    protected RiskConfig getCurrentConfig() {
        RiskConfig riskConfig = deserializeConfig(getFormatedConfigBase64FromRedis());
        return riskConfig != null ? riskConfig : initialConfig;
    }

    protected boolean checkRiskConfig(List<StrategyConfig> strategies) {
        for(StrategyConfig strategyConfig : strategies) {

            if(EStrategy.of(strategyConfig.getStrategyId()) == null) {
                String logMessage = "check strategy config found error: strategy " + strategyConfig.getStrategyId() + " does not exist.";
                RuntimeException exception = new RuntimeException(logMessage);
                log.atError().withMarker(LogMarkers.DreamRiskEngine)
                        .withThrowable(exception)
                        .log(logMessage);
                return false;
            }
        }
        return true;
    }

    private RiskConfig loadStrategies() {
        RiskConfig riskConfig = deserializeConfig(getFormatedConfigBase64FromRedis());

        // 优先从Redis中取数据，取不到或Redis中的配置文件版本较低则使用默认配置
        if (riskConfig != null && riskConfig.getVersion() > initialConfig.getVersion()) {

            // 版本号大于当前版本才加载
            if (riskConfig.getVersion() > currentConfigVersion) {
                log.info(LogMarkers.DreamRiskEngine, "Reloading strategies from Redis, version: {}", riskConfig.getVersion());
                setRiskConfig(riskConfig);
            }

            return riskConfig;
        }

        setRiskConfig(initialConfig);
        return initialConfig;
    }

    private RiskConfig deserializeConfig(String configBase64) {

        if(configBase64 == null) {
            return null;
        }

        try {
            byte[] decodedBytes = Base64.getDecoder().decode(configBase64);
            String yamlContent = new String(decodedBytes, StandardCharsets.UTF_8);

            RiskYamlConfig riskYamlConfig = yamlMapper.readValue(yamlContent, RiskYamlConfig.class);

            if (riskYamlConfig != null) {
                return riskYamlConfig.getRiskControl();
            } else {
                return null;
            }

        } catch (IOException e) {
            log.atError().withThrowable(e).withMarker(LogMarkers.DreamRiskEngine)
                    .log("Failed to deserialize YAML config, error: {}", e.getMessage());
            return null;
        }

    }

    private String getFormatedConfigBase64FromRedis() {
        String configBase64 = redisTemplate.opsForValue().get(RedisKeys.RiskEngineConfigBase64);

        if (configBase64 != null && configBase64.startsWith("\"") && configBase64.endsWith("\"")) {
            configBase64 = configBase64.substring(1, configBase64.length() - 1);
        }

        return configBase64;
    }

    private void setRiskConfig(RiskConfig riskConfig) {
        currentConfigVersion = riskConfig.getVersion();
        strategyMap = getStrategyMap(riskConfig.getStrategies());
    }


    private Map<String, StrategyConfig> getStrategyMap(List<StrategyConfig> strategies) {
        return strategies.stream()
                .collect(Collectors.toMap(
                        StrategyConfig::getStrategyId,
                        Function.identity(),
                        (existing, replacement) -> existing));
    }

}
